#!/bin/bash
# Copyright 2024 Hangzhou Yingyi Technology Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

declare -A Supported_Boards

function parse_boards_info() {
  while IFS= read -r line; do
    board=$(echo "$line" | jq -r '.board')
    plat=$(echo "$line" | jq -r '.plat')
    if [ "$board" == "null" ]; then
      Supported_Boards["$plat"]="$line"
    else
      Supported_Boards["$board"]="$line"
    fi
  done < <(jq -c '.[]' "board_info.json")
}

function print_boards() {
  echo "Currently Supported Boards:"
  echo "--------------------------------------------------------------"
  printf "%-10s | %-20s | %s\n" "Plat" "Board" "Brief"
  echo "--------------------------------------------------------------"
  for board in "${!Supported_Boards[@]}"; do
    plat=$(echo "${Supported_Boards[$board]}" | jq -r '.plat')
    brief=$(echo "${Supported_Boards[$board]}" | jq -r '.brief')
    board=$(echo "${Supported_Boards[$board]}" | jq -r '.board')
    if [ "$board" == "null" ]; then
      board="-"
    fi
    printf "%-10s | %-20s | %s\n" "$plat" "$board" "$brief"
  done
  echo "--------------------------------------------------------------"
}

function check_board() {
  if [[ -z "${Supported_Boards[$1]}" ]]; then
    echo "$1 is not supported, please check and try again."
    print_boards
    exit 1
  fi
}

function check_bsp_version() {
  local bsp_version=$(jq -r ".bsp.version" "$1")
  if [ "$bsp_version" == "null" ]; then
    echo -e "\033[33m [warning]: thers's no bsp version in config file("$1")"
    echo -e "           problems may occur during build process, skipping bsp version verification \033[0m"
    return
  fi
  local bsp_url=$(jq -r ".bsp.url" "$1")
  local remote_latest_commit=$(git ls-remote --tags "$bsp_url" | grep "$bsp_version^{}" | cut -f1)
  local local_latest_commit=$(git rev-parse HEAD)
  if [ "$local_latest_commit" != "$remote_latest_commit" ]; then
    echo -e "\033[31m [error]: The version of bsp in "$1" is inconsistent with the local version \033[0m"
    exit 1
  fi
}

function init_libs() {
  check_bsp_version "$1"
  # init tenon
  echo "Initializing build context, please wait several minutes.."
  echo "Initializing tenon..."
  rm -rf ../tenon
  clone_repo "$(jq -r '.tenon' "$1")" $(pwd)/.. "tenon"
  # init apps\libs\plats\drivers
  lib_type=(apps libs plats drivers)
  for type in "${lib_type[@]}"; do
    echo "Initializing $type..."
    rm -rf ../$type
    mkdir -p ../$type
    type_length=($(jq -r ".$type| length" "$1"))
    for ((num = 0; num < type_length; num++)); do
      clone_repo "$(jq -r ".$type[$num]" "$1")" $(pwd)/../${type}
    done
  done
  echo "Initializing finished."
}

# usage: clone_repo {repo_config_json} {parent_dir} {repo_name:optional}
function clone_repo() {
  repo_name=${3:-$(echo "$1" | jq -r ".name")}
  clone_to=${2}/${repo_name}
  repo_url=$(echo "$1" | jq -r ".url")
  repo_target=$(echo "$1" | jq -r ".version") && [[ ${repo_target} != null ]] ||
    repo_target=$(echo "$1" | jq -r ".branch") && [[ ${repo_target} != null ]] ||
    repo_target=${DEF_VERSION}
  rm -rf ${clone_to}
  git clone --recurse-submodules -q --depth 1 --branch ${repo_target} --no-tags ${repo_url} ${clone_to} 1>/dev/null 2>&1
}

function update_libs() {
  check_bsp_version "$1"
  # update tenon
  echo "Updating build context, please wait..."
  echo "Updating tenon..."
  update_repo "$(jq -r '.tenon' "$1")" $(pwd)/.. "tenon"
  # update apps\libs\plats\drivers
  lib_type=(apps libs plats drivers)
  for type in "${lib_type[@]}"; do
    echo "updating $type..."
    type_length=($(jq -r ".$type| length" "$1"))
    for ((num = 0; num < type_length; num++)); do
      update_repo "$(jq -r ".$type[$num]" "$1")" $(pwd)/../${type}
    done
  done
  echo "Updating finished."
}

# usage: update_repo {repo_config_json} {parent_dir} {repo_name:optional}
function update_repo() {
  repo_name=${3:-$(echo "$1" | jq -r ".name")}
  repo_path=${2}/${repo_name}
  repo_url=$(echo "$1" | jq -r ".url")
  repo_target=$(echo "$1" | jq -r ".version") && [[ ${repo_target} != null ]] ||
    repo_target=$(echo "$1" | jq -r ".branch") && [[ ${repo_target} != null ]] ||
    repo_target=${DEF_VERSION}

  if [ -d "${repo_path}" ]; then
    echo "Repository ${repo_name} already exists. Switching to ${repo_target}..."
    cd ${repo_path}
    if [ -n "$(git diff --name-only)" ] || [ -n "$(git diff --cached --name-only)" ]; then
      echo -e "\033[31m [error]: There are changes in the workspace or the index of ${repo_name}. Please commit or stash them before updating. \033[0m"
      exit
    fi
    git fetch ${repo_url} ${repo_target} -q --depth 1
    git checkout FETCH_HEAD 1>/dev/null 2>&1
    cd - 1>/dev/null 2>&1
  else
    echo "cloning ..."
    clone_repo "$1" "$2" "$3"
  fi
}

BUILD_HELP_MESSAGE=$(
  cat <<EOF
* Usage: 
  ./build.sh [Sub Command] [--board=|--plat=] [--kernel=]

* Examples:
    Initialize working context:
      ./build.sh --init-libs=version-management/lib_config_Tenon_v0.1.0.json

    Update working context:
      ./build.sh --update-libs=version-management/lib_config_Tenon_v0.1.0.json

    Build TenonOS
      ./build.sh [--board=|--plat=] [--kernel=] [Other Options]

    Pack TenonOS only
      ./build.sh pack [--board=|--plat=] [--kernel=]

* Sub Command(Optional):
    pack
                Packing boot files and Tenon image without building or compiling.

    clean
                Delete all files created by build for all libraries including final images, but 
                keep fetched files.
    distclean
                Delete build directory and configurations (including .config).

* Options:
    --init-libs=<pat/to/config>
                Optional. Create working directories, and clone lib repo described in config
                file. ***No need to be used with a command.*** Please use the version control file 
                stored in the version-management directory as a configuration template.

    --update-libs=<pat/to/config>
                Optional. Make each lib repo in the working directory consistent with the version 
                described in lib_config.json. ***No need to be used with a command.*** 
                Please use the version control file stored in the version-management directory 
                as a configuration template.

    --board=,   -b=
                One of '--board' or '--plat' is required. Specify a board to build the os image.

    --plat=,    -p=
                One of '--board' or '--plat' is required. Specify a plat to build the os image.
                Use this option when building 'virtual-plat' image like kvm.

    --kernel=,  -k=
                Optional. Path to the tenon. Default: 'bsp_workdir/tenon'.

    --app=,     -a=
                Optional. Path to the application if you want to build it within TenonOS.

    --config=,  -c=
                Optional. Specify a configuration file used to build TenonOS. Use 'defconfig' in
                plat or board directory if not set

    --help, -h
                Display this help message and exit.

    --support-boards, -s
                Display supported board information and exit. Use this option without any command.
EOF
)

function print_help() {
  printf "%s\n" "$BUILD_HELP_MESSAGE"
}
